/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using Leap.Unity.Interaction.Internal; using Leap.Unity.Space; using System; using System.Collections.Generic; using UnityEngine; namespace Leap.Unity.Interaction { /// /// ActivityManager is a wrapper around PhysX sphere queries for arbitrary Unity objects. /// "Active" objects are objects found in the latest query. It's also possible to get the /// sets of objects that just began or stopped being active since the last query; this /// requires enabling the trackStateChanges setting. /// public class ActivityManager { /// The radius of the query in world-space. public float activationRadius; /// /// The layer mask against which to query for active objects. The ActivityManager will only /// find objects in these layers. By default, the ActivityManager will query all layers, but /// this is highly inefficient. /// /// See SingleLayer and use bitwise operations on their layerMasks for a convenient way to /// express layer masks. /// /// public int activationLayerMask = ~0; /// /// This function, if set to a non-null value, overrides the activationLayerMask /// setting with the result of calling the function. Use this if your application /// state will modify the layer mask to use for the activity manager. /// public Func activationLayerFunction = null; /// /// This is the function by which the ActivityManager converts the Colliders it finds /// through PhysX queries into Ts to be placed in the ActiveObjects set. /// /// Only objects with at least one Collider for which this function returns a non-null T /// will be added to the ActiveObjects set. /// public Func filter = null; private HashSet _activeObjects = new HashSet(); /// /// Returns the currently "active" objects -- objects that were within the latest sphere query. /// public HashSet ActiveObjects { get { return _activeObjects; } } /// /// If set to true, BeganActive and EndedActive will be calculated and populated every time a /// new query occurs. /// public bool trackStateChanges = false; private HashSet _activeObjectsLastFrame = new HashSet(); private HashSet _beganActiveObjects = new HashSet(); /// /// If trackStateChanges is enabled (on by default), contains the objects that just started being active since /// the last query. /// public HashSet BeganActive { get { return _beganActiveObjects; } } private HashSet _endedActiveObjects = new HashSet(); /// /// If trackStateChanges is enabled (on by default), contains the objects that just stopped being active since /// the last query. /// public HashSet EndedActive { get { return _endedActiveObjects; } } public ActivityManager(float activationRadius, Func filter) { this.activationRadius = activationRadius; this.filter = filter; } [ThreadStatic] private static Collider[] s_colliderResultsBuffer = new Collider[32]; public void UpdateActivityQuery(Vector3? queryPosition, List spaces = null) { using (new ProfilerSample("Update Activity Manager")) { using (new ProfilerSample("Initialize Buffers")) { _activeObjects.Clear(); // Make sure collider results buffer exists (for other threads; see ThreadStatic) if (s_colliderResultsBuffer == null || s_colliderResultsBuffer.Length < 32) { s_colliderResultsBuffer = new Collider[32]; } } // Update object activity by activation radius around the query position (PhysX colliders necessary). // queryPosition can be null, in which case no objects will be found, and they will all become inactive. if (queryPosition.HasValue) { Vector3 actualQueryPosition = queryPosition.GetValueOrDefault(); int count = GetSphereColliderResults(actualQueryPosition, ref s_colliderResultsBuffer); UpdateActiveList(count, s_colliderResultsBuffer); using (new ProfilerSample("Check GUI Spaces")) { if (spaces != null) { //Check once in each of the GUI's subspaces foreach (LeapSpace space in spaces) { count = GetSphereColliderResults(transformPoint(actualQueryPosition, space), ref s_colliderResultsBuffer); UpdateActiveList(count, s_colliderResultsBuffer); } } } } using (new ProfilerSample("Track State Changes")) { if (trackStateChanges) { _endedActiveObjects.Clear(); _beganActiveObjects.Clear(); foreach (var behaviour in _activeObjects) { if (!_activeObjectsLastFrame.Contains(behaviour)) { _beganActiveObjects.Add(behaviour); } } foreach (var behaviour in _activeObjectsLastFrame) { if (!_activeObjects.Contains(behaviour)) { _endedActiveObjects.Add(behaviour); } } _activeObjectsLastFrame.Clear(); foreach (var behaviour in _activeObjects) { _activeObjectsLastFrame.Add(behaviour); } } } } } private int GetSphereColliderResults(Vector3 position, ref Collider[] resultsBuffer) { using (new ProfilerSample("GetSphereColliderResults()")) { int layerMask = activationLayerFunction == null ? activationLayerMask : activationLayerFunction(); int overlapCount = 0; while (true) { overlapCount = Physics.OverlapSphereNonAlloc(position, activationRadius, resultsBuffer, layerMask, QueryTriggerInteraction.Collide); if (overlapCount < resultsBuffer.Length) { break; } else { // Non-allocating sphere-overlap fills the existing _resultsBuffer array. // If the output overlapCount is equal to the array's length, there might be more collision results // that couldn't be returned because the array wasn't large enough, so try again with increased length. // The _in, _out argument setup allows allocating a new array from within this function. resultsBuffer = new Collider[resultsBuffer.Length * 2]; } } return overlapCount; } } private void UpdateActiveList(int numResults, Collider[] results) { if (filter == null) { Debug.LogWarning("[ActivityManager] No filter provided for this ActivityManager; no objects will ever be returned." + "Please make sure you specify a non-null filter property of any ActivityManagers you wish to use."); return; } for (int i = 0; i < numResults; i++) { T obj = filter(results[i]); if (obj != null) { _activeObjects.Add(obj); } } } private Vector3 transformPoint(Vector3 worldPoint, LeapSpace space) { Vector3 localPalmPos = space.transform.InverseTransformPoint(worldPoint); return space.transform.TransformPoint(space.transformer.InverseTransformPoint(localPalmPos)); } } }